"""
Pytest configuration and shared fixtures for GCR Solver Manager tests.
"""

import os
import tempfile
import pytest
from pathlib import Path
from unittest.mock import Mock, AsyncMock
from typing import Dict, Any, Generator
import asyncio

# Import our modules for testing (with fallback mocks for modules not yet implemented)
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

try:
    from core.config import Config
    from core.exceptions import GSMError
except ImportError:
    # Mock these for now until modules are implemented
    Config = Mock()
    GSMError = Exception

try:
    from database.session import DatabaseSession
    from database.models import Base
except ImportError:
    # Mock these for now until modules are implemented
    DatabaseSession = Mock()
    Base = Mock()


@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
    """Create a temporary directory for test files."""
    with tempfile.TemporaryDirectory() as temp_dir:
        yield Path(temp_dir)


@pytest.fixture
def test_config(temp_dir: Path) -> Dict[str, Any]:
    """Provide test configuration."""
    return {
        'environment': 'test',
        'debug': True,
        'log_level': 'DEBUG',
        'database': {
            'url': f'sqlite:///{temp_dir}/test.db',
            'echo': False,
        },
        'build': {
            'source_dir': str(temp_dir / 'src'),
            'build_dir': str(temp_dir / 'build'),
            'parallel_jobs': 2,
            'cache_enabled': True,
        },
        'test': {
            'default_nproc': 2,
            'default_resolution': 1.0,
            'test_timeout': 60,
            'remote_enabled': False,
        },
        'logs': {
            'log_dir': str(temp_dir / 'logs'),
            'retention_days': 7,
            'compression_enabled': False,
        },
        'analysis': {
            'norm_tolerance': 1.0e-10,
            'performance_baseline_days': 1,
        },
        'api': {
            'host': '127.0.0.1',
            'port': 8001,  # Different port for testing
            'debug': True,
        }
    }


@pytest.fixture
async def db_session(test_config: Dict[str, Any]) -> Generator[DatabaseSession, None, None]:
    """Create a test database session."""
    # This will be implemented once database module is created
    db_session = Mock(spec=DatabaseSession)
    db_session.commit = AsyncMock()
    db_session.rollback = AsyncMock()
    db_session.close = AsyncMock()
    
    yield db_session
    
    await db_session.close()


@pytest.fixture
def mock_build_config():
    """Mock build configuration for testing."""
    config = Mock()
    config.id = "test-build-config-id"
    config.name = "test-build"
    config.gpu_type = "cuda"
    config.solver_type = "gcr"
    config.communication_type = "mpi"
    config.debug_mode = True
    config.cmake_args = {"CMAKE_BUILD_TYPE": "Debug"}
    config.environment_vars = {"CC": "gcc", "CXX": "g++"}
    return config


@pytest.fixture
def mock_test_run():
    """Mock test run for testing."""
    test_run = Mock()
    test_run.id = "test-run-id"
    test_run.build_config_id = "test-build-config-id"
    test_run.name = "test-run"
    test_run.parameters = {
        "nproc": 4,
        "resolution": 1.0,
        "maxit": 20
    }
    test_run.status = "PENDING"
    test_run.exit_code = None
    test_run.slurm_job_id = None
    return test_run


@pytest.fixture
def mock_log_entry():
    """Mock log entry for testing."""
    log_entry = Mock()
    log_entry.id = "test-log-entry-id"
    log_entry.test_run_id = "test-run-id"
    log_entry.log_type = "RUNTIME"
    log_entry.file_path = "/tmp/test.log"
    log_entry.content_hash = "abc123"
    log_entry.size = 1024
    log_entry.compressed = False
    log_entry.metadata = {"solver": "gcr", "gpu_type": "cuda"}
    return log_entry


@pytest.fixture
def sample_log_content() -> str:
    """Provide sample log content for testing."""
    return """
[DEBUG] Starting GCR solver test...
[INFO] Configuration: nproc=4, resolution=1.0
[CPU-GCR-debug] pi (s=-1) = 3.14159E+00
[CPU-GCR-debug] initial_residual (s=-1) = 8.55618E+02
[CPU-GCR-debug] h_r_norm_after_update (s=0) = 7.42531E+02
[CPU-GCR-debug] h_r_norm_after_update (s=1) = 6.31245E+02
[INFO] Solver converged after 10 iterations
[INFO] Final residual: 1.23456E-08
[DEBUG] Test completed successfully
    """.strip()


@pytest.fixture
def sample_norm_data() -> Dict[str, float]:
    """Provide sample norm data for testing."""
    return {
        "pi": 3.14159e+00,
        "initial_residual": 8.55618e+02,
        "h_r_norm_after_update_s0": 7.42531e+02,
        "h_r_norm_after_update_s1": 6.31245e+02,
        "final_residual": 1.23456e-08
    }


@pytest.fixture
def mock_cmake_process():
    """Mock subprocess for CMake operations."""
    process = Mock()
    process.returncode = 0
    process.stdout = "CMake configuration successful"
    process.stderr = ""
    process.communicate.return_value = (process.stdout, process.stderr)
    return process


@pytest.fixture
def mock_ssh_client():
    """Mock SSH client for remote operations."""
    ssh_client = Mock()
    ssh_client.connect = Mock()
    ssh_client.exec_command = Mock()
    ssh_client.close = Mock()
    
    # Mock command execution
    stdin = Mock()
    stdout = Mock()
    stderr = Mock()
    
    stdout.read.return_value = b"Command executed successfully"
    stderr.read.return_value = b""
    stdout.channel.recv_exit_status.return_value = 0
    
    ssh_client.exec_command.return_value = (stdin, stdout, stderr)
    
    return ssh_client


@pytest.fixture
def mock_slurm_job():
    """Mock SLURM job for testing."""
    job = Mock()
    job.job_id = "12345"
    job.job_name = "test-gcr-job"
    job.state = "RUNNING"
    job.nodes = ["node001"]
    job.partition = "gpu"
    job.submit_time = "2025-08-18T10:00:00"
    return job


class TestDatabaseMixin:
    """Mixin class to provide database testing utilities."""
    
    @pytest.fixture(autouse=True)
    async def setup_database(self, db_session):
        """Set up database for testing."""
        self.db = db_session
        # Create tables if needed
        # await Base.metadata.create_all(bind=db_session.bind)
        
    async def create_test_data(self):
        """Create test data in the database."""
        # This will be implemented by specific test classes
        pass
        
    async def cleanup_test_data(self):
        """Clean up test data after tests."""
        # This will be implemented by specific test classes
        pass


class TestAPIClient:
    """Test client for API testing."""
    
    def __init__(self, app):
        self.app = app
        # This will be a real test client once FastAPI is integrated
        
    async def get(self, url: str, **kwargs):
        """Mock GET request."""
        return Mock(status_code=200, json=lambda: {"status": "ok"})
        
    async def post(self, url: str, **kwargs):
        """Mock POST request.""" 
        return Mock(status_code=201, json=lambda: {"status": "created"})
        
    async def put(self, url: str, **kwargs):
        """Mock PUT request."""
        return Mock(status_code=200, json=lambda: {"status": "updated"})
        
    async def delete(self, url: str, **kwargs):
        """Mock DELETE request."""
        return Mock(status_code=204, json=lambda: None)


@pytest.fixture
def api_client():
    """Provide test API client."""
    return TestAPIClient(app=None)


# Skip markers for integration tests that require external resources
skip_if_no_database = pytest.mark.skipif(
    os.environ.get("TEST_DATABASE_URL") is None,
    reason="Database URL not provided"
)

skip_if_no_remote = pytest.mark.skipif(
    os.environ.get("TEST_REMOTE_HOST") is None,
    reason="Remote host not provided"
)

skip_if_no_slurm = pytest.mark.skipif(
    os.environ.get("TEST_SLURM_ENABLED") != "true",
    reason="SLURM not available"
)

# Test data directories
TEST_DATA_DIR = Path(__file__).parent / "data"
SAMPLE_LOGS_DIR = TEST_DATA_DIR / "sample_logs"
SAMPLE_CONFIGS_DIR = TEST_DATA_DIR / "sample_configs"